/* pkutil.c
 *
 * Copyright 2007-2008 OpenMoko, Inc.
 * Authored by Tick <tick@openmoko.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

#define  _GNU_SOURCE
#include <pkutil.h>
#include <debug.h>
#include <string.h>
#include <stdlib.h>
#include <pk-package-id.h>

static E_DBus_Connection* e_conn = NULL;
static E_DBus_Proxy *__e_proxy = NULL;

Pk_Operation * starts_a_new_translation(Pk_Operation * pko, char *tid);
static void pko_del(Pk_Operation *);

static double __keep_packagekit_alive_timer;
static void reset_packagekit_timer() {
    __keep_packagekit_alive_timer = ecore_time_get();
}

inline static void packagekitd_dbus_error_handling() {
    error("\n");
    system("killall packagekitd");
}

static int keep_packagekit_alive_watchdog(void *data) {
    DBusError _err;
    char *group = NULL;
    if ((ecore_time_get() - __keep_packagekit_alive_timer)
            > (KEEP_PACKAGEKIT_ALIVE_TIMEOUT >> 1) ) {
        // Do something
        dbus_error_init(&_err);
        if (!e_dbus_proxy_simple_call(pk_proxy(), "GetGroups", &_err,
                                      DBUS_TYPE_INVALID,
                                      DBUS_TYPE_STRING, &group,
                                      DBUS_TYPE_INVALID)) {
            error("Error message: %s\n", _err.message);
            dbus_error_free(&_err);
            return 0;
        }
        dbus_error_free(&_err);
        debug("To prevent timeout query the groups: %s\n", group);
    }
    reset_packagekit_timer();
    return 1;
}


const char * find_keyword(Operations_CB num) {
    static const char * __POM [] = {
        "ProgressChanged",
        "StatusChanged",
        "Finished",
        "ErrorCode",
        "Message",
        "Details",
        "Files",
        "Package",
        "RepoDetail",
        "UpdateDetail",
        NULL,
    };
    if (num < 0 || num >= END_OF_CB_SIG) {
        return NULL;
    }
    return __POM[num];
}

E_DBus_Connection * pk_e_dbus_init(void) {
    if (e_conn) return e_conn;

    debug("E_DBUS initialing. \n");
    if (!e_dbus_init()) {
        return NULL;
    }

    e_conn = e_dbus_bus_get(PK_DBUS_BUS);

    if (!e_conn) {
        error("Get %d bus failed!!\n", PK_DBUS_BUS);
        pk_e_dbus_shutdown();
        return NULL;
    }
    ecore_timer_add(KEEP_PACKAGEKIT_ALIVE_TIMEOUT, &keep_packagekit_alive_watchdog, NULL);
    return e_conn;
}

void pk_e_dbus_shutdown(void) {
    if (e_conn) {
        if (pk_proxy()) {
            e_dbus_proxy_destroy(pk_proxy());
            __e_proxy = NULL;
        }
        e_dbus_connection_close(e_conn);
        e_conn = NULL;
        e_dbus_shutdown();
    }
}

E_DBus_Proxy * pk_proxy(void) {
    if (__e_proxy) return __e_proxy;
    if (!pk_e_dbus_init()) {
        return NULL;
    }
    __e_proxy = e_dbus_proxy_new_for_name (pk_e_dbus_init(), PK_DBUS_SERVICE, PK_DBUS_PATH, PK_DBUS_INTERFACE);
    return __e_proxy;
}


static char * pk_get_tid(char **tid) {
    DBusError _err;
    if (tid == NULL) return NULL;
    dbus_error_init(&_err);
    if (!pk_proxy()) {
        error("Get Proxy Failed!!\n");
        return NULL;
    }
    if (!e_dbus_proxy_simple_call(pk_proxy(), "GetTid", &_err,
                                  DBUS_TYPE_INVALID,
                                  DBUS_TYPE_STRING, tid,
                                  DBUS_TYPE_INVALID)) {
        error("Error message: %s\n", _err.message);
#ifdef CONFIG_DEBUG
        packagekitd_dbus_error_handling();
        return NULL;
#else
        exit(0);
#endif

    }
    reset_packagekit_timer();
    dbus_error_free(&_err);
    debug("Get tid %s\n", *tid);
    return *tid;
}

int pk_repos_ping() {
    DBusError _err;
    char *status = NULL;
    dbus_error_init(&_err);
    if (!pk_proxy()) {
        error("Get Proxy Failed!!\n");
        return -1;
    }
    if (!e_dbus_proxy_simple_call(pk_proxy(), "GetNetworkState", &_err,
                                  DBUS_TYPE_INVALID,
                                  DBUS_TYPE_STRING, &status,
                                  DBUS_TYPE_INVALID)) {
        error("Error message: '%s' status: '%s'\n", _err.message, status);
        status = strdupa(_err.message);
        dbus_error_free(&_err);
        packagekitd_dbus_error_handling();
        // FIXME: It's dirty, will be fixed while pushing repos_ping stuff to packagekit
        if (!strcmp(status, "Message did not receive a reply (timeout by message bus)")) {
            return -1;
        }
        return 0;
    }
    if (!status)
        return 0;
    debug("Get Network status %s\n", status);
    return !strcmp (status, "online");
}

// Do not update the timestamp
static int __pko_timeout_check(Pk_Operation * pko) {
    if (!pko->timer)
        return 0;
    return !! ((time(NULL) - pko->timestamp) > TIMEOUT_THRESHOLD ? 1 : 0);
}

int pko_timeout_check(Pk_Operation * pko) {
    int now = time(NULL);
    int ret;
    if (!pko) return 0;
    if (!pko->timer) return 0;
    ret = __pko_timeout_check(pko);
    pko->timestamp = now;
    return !ret;
}



Pk_Operation * pko_new() {
    Pk_Operation *ans = (Pk_Operation *)malloc(sizeof(Pk_Operation));
    memset(ans, 0, sizeof(Pk_Operation));
    return ans;
}

static void pko_del(Pk_Operation *pko) {
    free(pko->tid);
    if (pko->timer)
        ecore_timer_del(pko->timer);
    pko->timer = NULL;
    debug("destroy proxy %p\n", pko->proxy);
    e_dbus_proxy_destroy(pko->proxy);
    memset(pko, 0, sizeof(Pk_Operation));
    free(pko);
}

// pk_marshal_VOID__STRING_STRING_STRING
static void pk_default_message_cb (void *data, DBusMessage *message) {
    char *_message;
    char *detail;
    DBusError _err;
    Pk_Operation * pko = (Pk_Operation *)data;
    pko_timeout_check(pko);

    dbus_error_init(&_err);
    if (!dbus_message_get_args(message, &_err,
                               DBUS_TYPE_STRING, &_message,
                               DBUS_TYPE_STRING, &detail,
                               DBUS_TYPE_INVALID )) {
        error("Error Message: '%s'\n", _err.message);
        dbus_error_free(&_err);
        return;
    }
    dbus_error_free(&_err);

    debug("Transcation: Message '%s' Detail: '%s'\n", _message, detail);
}

// pk_marshal_VOID__STRING_STRING_STRING
static void pk_default_error_cb (void *data, DBusMessage *message) {
    char *_message;
    char *detail;
    DBusError _err;
    Pk_Operation * pko = (Pk_Operation *)data;
    pko_timeout_check(pko);

    dbus_error_init(&_err);
    if (!dbus_message_get_args(message, &_err,
                               DBUS_TYPE_STRING, &_message,
                               DBUS_TYPE_STRING, &detail,
                               DBUS_TYPE_INVALID )) {
        error("Error Message: '%s'\n", _err.message);
        dbus_error_free(&_err);
        return;
    }
    dbus_error_free(&_err);

    debug("Transcation: Error '%s' Detail: '%s'\n", _message, detail);
}


// pk_marshal_VOID__STRING_STRING
static void pk_default_status_change_cb(void *data, DBusMessage *message) {
    char *status_text;
    DBusError _err;
    Pk_Operation * pko = (Pk_Operation *)data;
    pko_timeout_check(pko);
    dbus_error_init(&_err);
    if (!dbus_message_get_args(message, &_err,
                               DBUS_TYPE_STRING, &status_text,
                               DBUS_TYPE_INVALID )) {
        error("Error Message: '%s'\n", _err.message);
        dbus_error_free(&_err);
        return;
    }
    if (!strcmp(status_text, "finished")) {
        ecore_timer_del(pko->timer);
        pko->timer = NULL;
    }
    dbus_error_free(&_err);
    debug("Transcation %s: Status chages to %s\n", pko->tid, status_text);
}

// pk_marshal_VOID__STRING_STRING_UINT  Finished
void pk_default_get_finished_cb (void *data, DBusMessage *message) {
    char *exit_text;
    unsigned int runtime;
    Pk_Operation * pko = (Pk_Operation *)data;
    DBusError _err;
    pko_timeout_check(pko);
    dbus_error_init(&_err);
    if (!dbus_message_get_args(message, &_err,
                               DBUS_TYPE_STRING, &exit_text,
                               DBUS_TYPE_UINT32,   &runtime,
                               DBUS_TYPE_INVALID )) {
        error("Error Message: '%s'\n", _err.message);
    } else {
        debug("exit_text: %s\n", exit_text);
        debug("runtime %d\n", runtime);
    }
    dbus_error_free(&_err);
    pko_del(pko);
    return;
}

Pk_Operation * starts_a_new_translation(Pk_Operation * pko, char * tid) {
    int i;
    E_DBus_Proxy *proxy;
    if (tid == NULL) {
        error("tid is NULL!!\n");
        return NULL;
    }
    if (pko == NULL) {
        pko = (Pk_Operation *)malloc(sizeof(Pk_Operation));
        memset(pko, 0, sizeof(Pk_Operation));
    }
    pko_timeout_check(pko);
    pko->timer = ecore_timer_add(60, &pko_timeout_cancel_transaction, pko);
    // Use default Finished event
    if (!pko->Operations[Finished_sig]) {
        pko->Operations[Finished_sig] 		= &pk_default_get_finished_cb;
    }
    if (!pko->Operations[StatusChanged_sig]) {
        pko->Operations[StatusChanged_sig] 	= &pk_default_status_change_cb;
    }
    if (!pko->Operations[Message_sig]) {
        pko->Operations[Message_sig] 		= &pk_default_message_cb;
    }
    if (!pko->Operations[ErrorCode_sig]) {
        pko->Operations[ErrorCode_sig] 		= &pk_default_error_cb;
    }
    proxy = e_dbus_proxy_new_for_name (pk_e_dbus_init(),
                                       PK_DBUS_SERVICE,
                                       tid,
                                       PK_DBUS_INTERFACE_TRANSACTION);
    if (proxy == NULL) {
        error("proxy new for name %s is NULL (failed)!!\n", tid);
        free(pko);
        return NULL;
    }
    pko->proxy = proxy;
    pko->tid = strdup(tid);
    debug("Trans %s proxy is %p\n", pko->tid, pko->proxy);
    for (i = 0;i < END_OF_CB_SIG ;i++) {
        if (!pko->Operations) {
            continue;
        }
        e_dbus_proxy_connect_signal(pko->proxy, find_keyword(i), pko->Operations[i], pko);
    }
    return pko;
}


static void __pk_search(Pk_Operation * pko, const char *name, const char *what) {
    DBusError _err;
    char *tid;
    int ret;
    const char *filter = OMFILTER;
    if (!name || !what) {
        error("Search for what?\n");
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, what , &_err,
                                   DBUS_TYPE_STRING, &filter,
                                   DBUS_TYPE_STRING, &name,
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("Ret==%d pko->proxy=%p (%s %s)\n", ret , pko->proxy, what, name);
    if (ret == 0) {
        error("Trans: %s Error message: %s!!\n", tid, _err.message);
    }
    dbus_error_free(&_err);
}


void pk_search_name(Pk_Operation * pko, const char *name) {
    __pk_search(pko, name, "SearchName");
}



void pk_search_group(Pk_Operation * pko, const char *group) {
    __pk_search(pko, group, "SearchGroup");
}

static void __pk_get_updates(Pk_Operation * pko, const char *what) {
    DBusError _err;
    char *tid;
    int ret;
    const char *filter = OMFILTER;
    if (!what) {
        error("Search for what?\n");
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, what , &_err,
                                   DBUS_TYPE_STRING, &filter,
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("Ret==%d pko->proxy=%p (%s)\n", ret , pko->proxy, what);
    if (ret == 0) {
        error("Error message: %s!!\n", _err.message);
    }
    dbus_error_free(&_err);
}


void pk_get_updates(Pk_Operation * pko) {
    __pk_get_updates(pko, "GetUpdates");
}

static void __pk_action_to_package(Pk_Operation * pko, const char *packageid, const char *action) {
    DBusError _err;
    char *tid;
    int ret;
    if (!packageid || !action) {
        error("Do what?\n");
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, action , &_err,
                                   DBUS_TYPE_STRING, &packageid,
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("%s Ret==%d\n", action, ret);
    if (ret == 0) {
        error("Error message: %s!!\n", _err.message);
    }
    dbus_error_free(&_err);
}

void pk_get_details(Pk_Operation * pko, const char * packageid) {
    __pk_action_to_package(pko, packageid, "GetDetails");
}

static void __pk_action_to_packages(Pk_Operation * pko, const char *packageid, const char *action) {
    DBusError _err;
    char *tid;
    const int pkgs = 1;
    char **pkids;
    pkids = (char **)malloc((pkgs + 1) * sizeof(char *));
    pkids[0] = (char *)packageid;
    pkids[1] = NULL;

    int ret;
    if (!packageid || !action) {
        error("Do what?\n");
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, action , &_err,
                                   DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &pkids, pkgs,
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("%s Ret==%d\n", action, ret);
    if (ret == 0) {
        error("Error message: %s!!\n", _err.message);
    }
    dbus_error_free(&_err);
    free(pkids);
}

void pk_install_package(Pk_Operation * pko, const char * packageid) {
    __pk_action_to_packages(pko, packageid, "InstallPackages");
}

void pk_update_package(Pk_Operation * pko, const char * packageid) {
    __pk_action_to_packages(pko, packageid, "UpdatePackages");
}


void _pk_uninstall_package(Pk_Operation * pko, const char * packageid, int allow_dep, int autoremove) {
    DBusError _err;
    char *tid;
    int ret;
    char **pkids;
    const int pkgs = 1;
    pkids = (char **)malloc((pkgs + 1) * sizeof(char *));
    pkids[0] = (char *)packageid;
    pkids[1] = NULL;

    if (!packageid) {
        error("Remove what?\n");
        free(pkids);
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        free(pkids);
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, "RemovePackages" , &_err,
                                   DBUS_TYPE_ARRAY, DBUS_TYPE_STRING, &pkids, pkgs,
                                   DBUS_TYPE_BOOLEAN, &allow_dep,
                                   DBUS_TYPE_BOOLEAN, &autoremove,
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("Ret==%d\n", ret);
    if (ret == 0) {
        error("Error message: %s!!\n", _err.message);
    }
    dbus_error_free(&_err);
    free(pkids);
}


void pk_uninstall_package(Pk_Operation * pko, const char * packageid) {
    // default turn on the auto install flag.
    _pk_uninstall_package(pko, packageid, 1, 1);
}

static void __pk_single_action(Pk_Operation * pko, const char *action) {
    DBusError _err;
    char *tid;
    int ret;
    if (!action) {
        error("Do what?\n");
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, action , &_err,
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("Ret==%d\n", ret);
    if (ret == 0) {
        error("Error message: %s!!\n", _err.message);
    }
    dbus_error_free(&_err);
}

void pk_update_system(Pk_Operation * pko) {
    __pk_single_action(pko, "UpdateSystem");
}

static void __pk_refresh_action(Pk_Operation * pko, const char *action) {
    DBusError _err;
    char *tid;
    int ret;
    int force = 1;
    if (!action) {
        error("Do what?\n");
        return;
    }
    if (!pk_get_tid(&tid)) {
        error("get tid failed!!\n");
        return;
    }
    dbus_error_init(&_err);
    starts_a_new_translation(pko, tid);
    ret = e_dbus_proxy_simple_call(pko->proxy, action , &_err,
                                   DBUS_TYPE_BOOLEAN, &force,   // force
                                   DBUS_TYPE_INVALID,
                                   DBUS_TYPE_INVALID);
    debug("Ret==%d\n", ret);
    if (ret == 0) {
        error("Error message: %s!!\n", _err.message);
    }
    dbus_error_free(&_err);
}

// Do System database update such as opkg update
void pk_refresh_system(Pk_Operation * pko) {
    __pk_refresh_action(pko, "RefreshCache");
}


int get_pk_name_from_id (char *packageid, char *summary, char *name, int bufsize) {
    PkPackageId * pid;
    char *ptr, *n_ptr;
    const char *MAGIC_ALIAS = "alias::";
    if (!packageid || !name) {
        return 0;
    }
    if (!pk_package_id_check(packageid)) {
        if (packageid && strlen(packageid) > 0) {
            error("This packageid '%s' may be wrong!!\n", packageid);
            strncpy(name, packageid, bufsize);
            return 1;
        }
        error("Bad Packageid!!\n");
        name[0] = '\0';
        return 0;
    }
    if (summary && strstr(summary, MAGIC_ALIAS)) {
        ptr = strstr(summary, MAGIC_ALIAS) + strlen(MAGIC_ALIAS);
        n_ptr = name;
        *n_ptr = '\0';
        if (ptr) {
            while (*ptr != ' ' &&
                    *ptr != ';' &&
                    *ptr != '\0' &&
                    (size_t)(n_ptr - name) <= bufsize) {
                if (*ptr != '_' )
                    *n_ptr++ = *ptr++;
                else {
                    *n_ptr++ = ' ';
                    ptr++;
                }
                *n_ptr = '\0';
            }
            return 1;
        }
    }
    pid	= pk_package_id_new_from_string(packageid);
    strncpy (name, pid->name, bufsize);
    pk_package_id_free(pid);
    return 1;
}

#include <gears.h>
extern void remove_action_progressbar();
int pko_timeout_cancel_transaction(void * data) {
    Pk_Operation * pko = (Pk_Operation *) data;
    if (!pko->timer)
        return 0;
    if (__pko_timeout_check(pko)) { // Timeout
        error("ERROR: pko %p timeouted!!\n", pko->proxy);
        remove_action_progressbar();
        message_alert("System timeout! <p>Restart Assassin is recommended.", FATAL_ERROR, NULL);
        pko_del(pko);
        packagekitd_dbus_error_handling();
        return 0;
    }
    return 1;
}

